package com.jwgf.protocolstack;

import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

import com.jwgf.common.AppContext;
import com.jwgf.protocolstack.CommunicationStateListener.ReadFailedListener;
import com.jwgf.protocolstack.CommunicationStateListener.ReadSucceedListener;
import com.jwgf.protocolstack.CommunicationStateListener.SuccessRateListener;
import com.jwgf.protocolstack.CommunicationStateListener.WriteFailedListener;
import com.jwgf.protocolstack.CommunicationStateListener.WriteSucceedListener;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.functions.Consumer;

public class ProtocolStack {

    public static final String TAG = "ProtocolStack";

    private ProtocolStackState state = new NullState();

    private Comparator<byte[]> comparator;
    private List<byte[]> cmdListNormal = Collections.synchronizedList(new ArrayList<byte[]>());
    private List<byte[]> cmdListPriority = Collections.synchronizedList(new ArrayList<byte[]>());
    private List<byte[]> cmdListExcuted = Collections.synchronizedList(new ArrayList<byte[]>());
    private byte[] cmdExcuting;
    //    private LoopThread innerThread;
    private InnerHandler innerHandler;
    private HandlerThread handlerThread;
    private boolean isComStateOK = true;
    private boolean isIdle = true;

    private ArrayList<ReadFailedListener> readFailedListeners = new ArrayList<ReadFailedListener>();
    private ArrayList<ReadSucceedListener> readSucceedListeners = new ArrayList<ReadSucceedListener>();
    private ArrayList<WriteSucceedListener> writeSucceedListeners = new ArrayList<WriteSucceedListener>();
    private ArrayList<WriteFailedListener> writeFailedListeners = new ArrayList<WriteFailedListener>();
    private ArrayList<SuccessRateListener> successRateListeners = new ArrayList<SuccessRateListener>();

    private int totalExectuteNum;
    private int totalSucceedNum;

    public static HashSet<Integer> spindleNum = new HashSet<Integer>();

    public ProtocolStack() {
        if (innerHandler == null) {
            handlerThread = new HandlerThread("protocol");
            handlerThread.start();
            innerHandler = new InnerHandler(handlerThread.getLooper());
        }
    }

    public void registerReadFailedListener(ReadFailedListener rfl) {
        if (rfl != null && !readFailedListeners.contains(rfl))
            readFailedListeners.add(rfl);
    }

    public void unregisterReadFailedListener(ReadFailedListener rfl) {
        if (rfl != null)
            readFailedListeners.remove(rfl);
    }

    public void notifyReadFailedListeners() {
        if (cmdExcuting != null) {
            byte[] copy = Arrays.copyOf(cmdExcuting, cmdExcuting.length);
            Observable.just(copy).observeOn(AndroidSchedulers.mainThread())
                    .subscribe(new Consumer<byte[]>() {
                        @Override
                        public void accept(byte[] bytes) throws Throwable {
                            Iterator<ReadFailedListener> itor = ((ArrayList) readFailedListeners.clone()).iterator();
                            while (itor.hasNext()) {
                                ReadFailedListener listener = itor.next();
                                listener.readFailed(ProtocolStack.this, bytes);
                            }
                        }
                    });

        }
//        if (Looper.myLooper() == Looper.getMainLooper()) {
//            Iterator<ReadFailedListener> itor = ((ArrayList) readFailedListeners.clone()).iterator();
//            byte[] cmdExecuted = getCmdExecuted();
//            while (itor.hasNext()) {
//                ReadFailedListener listener = itor.next();
//                listener.readFailed(this, cmdExecuted);
//            }
//        } else {
//            // Run this function on the main thread.
//            if (cmdExcuting != null) {
//                byte[] copy = Arrays.copyOf(cmdExcuting, cmdExcuting.length);
//                cmdListExcuted.add(copy);
//            } else {
//                cmdListExcuted.add(cmdExcuting);
//            }
//            Handler mainHandler = new Handler(AppContext.getInstance().getMainLooper());
//            mainHandler.post(new Runnable() {
//                @Override
//                public void run() {
//                    notifyReadFailedListeners();
//                }
//            });
//        }
    }

    public void registerReadSucceedListener(ReadSucceedListener rsl) {
        if (rsl != null && !readSucceedListeners.contains(rsl))
            readSucceedListeners.add(rsl);
    }

    public void unregisterReadSucceedListener(ReadSucceedListener rsl) {
        if (rsl != null)
            readSucceedListeners.remove(rsl);
    }

    public void notifyReadSucceedListeners(final byte[] data) {
        totalSucceedNum++;
        byte[] copy = {0};
        if (cmdExcuting != null) {
            copy = Arrays.copyOf(cmdExcuting, cmdExcuting.length);
        }
        Observable.just(copy).observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<byte[]>() {
                    @Override
                    public void accept(byte[] bytes) throws Throwable {
                        Iterator<ReadSucceedListener> itor = ((ArrayList) readSucceedListeners.clone()).iterator();
                        while (itor.hasNext()) {
                            ReadSucceedListener listener = itor.next();
                            listener.readSucceed(ProtocolStack.this, bytes, data);
                        }
                    }
                });


//        if (Looper.myLooper() == Looper.getMainLooper()) {
//            Iterator<ReadSucceedListener> itor = ((ArrayList) readSucceedListeners.clone()).iterator();
//            byte[] cmdExecuted = getCmdExecuted();
//            while (itor.hasNext()) {
//                ReadSucceedListener listener = itor.next();
//                listener.readSucceed(this, cmdExecuted, data);
//            }
//        } else {
//            // Run this function on the main thread.
//            if (cmdExcuting != null) {
//                byte[] copy = Arrays.copyOf(cmdExcuting, cmdExcuting.length);
//                cmdListExcuted.add(copy);
//            } else {
//                cmdListExcuted.add(cmdExcuting);
//            }
//            Handler mainHandler = new Handler(AppContext.getInstance().getMainLooper());
//            mainHandler.post(new Runnable() {
//                @Override
//                public void run() {
//                    notifyReadSucceedListeners(data);
//                }
//            });
//        }
    }

    public void registerWriteFailedListener(WriteFailedListener wfl) {
        if (wfl != null && !writeFailedListeners.contains(wfl))
            writeFailedListeners.add(wfl);
    }

    public void unregisterWriteFailedListener(WriteFailedListener wfl) {
        if (wfl != null)
            writeFailedListeners.remove(wfl);
    }

    public void notifyWriteFailedListeners() {
        if (cmdExcuting != null) {
            byte[] copy = Arrays.copyOf(cmdExcuting, cmdExcuting.length);
            Observable.just(copy).observeOn(AndroidSchedulers.mainThread())
                    .subscribe(new Consumer<byte[]>() {
                        @Override
                        public void accept(byte[] bytes) throws Throwable {
                            Iterator<WriteFailedListener> itor = ((ArrayList) writeFailedListeners.clone()).iterator();
                            while (itor.hasNext()) {
                                WriteFailedListener listener = itor.next();
                                listener.writeFailed(ProtocolStack.this, bytes);
                            }
                        }
                    });

        }
//        if (Looper.myLooper() == Looper.getMainLooper()) {
//            Iterator<WriteFailedListener> itor = ((ArrayList) writeFailedListeners.clone()).iterator();
//            byte[] cmdExecuted = getCmdExecuted();
//            while (itor.hasNext()) {
//                WriteFailedListener listener = itor.next();
//                listener.writeFailed(this, cmdExecuted);
//            }
//        } else {
//            // Run this function on the main thread.
//            byte[] copy = Arrays.copyOf(cmdExcuting, cmdExcuting.length);
//            cmdListExcuted.add(copy);
//            Handler mainHandler = new Handler(AppContext.getInstance().getMainLooper());
//            mainHandler.post(new Runnable() {
//                @Override
//                public void run() {
//                    notifyWriteFailedListeners();
//                }
//            });
//        }
    }

    public void registerWriteSucceedListener(WriteSucceedListener wsl) {
        if (wsl != null && !writeSucceedListeners.contains(wsl))
            writeSucceedListeners.add(wsl);
    }

    public void unregisterWriteSucceedListener(WriteSucceedListener wsl) {
        if (wsl != null)
            writeSucceedListeners.remove(wsl);
    }

    public void notifyWriteSucceedListeners() {
        totalSucceedNum++;
        if (cmdExcuting != null) {
            byte[] copy = Arrays.copyOf(cmdExcuting, cmdExcuting.length);
            Observable.just(copy).observeOn(AndroidSchedulers.mainThread())
                    .subscribe(new Consumer<byte[]>() {
                        @Override
                        public void accept(byte[] bytes) throws Throwable {
                            Iterator<WriteSucceedListener> itor = ((ArrayList) writeSucceedListeners.clone()).iterator();
                            while (itor.hasNext()) {
                                WriteSucceedListener listener = itor.next();
                                listener.writeSucceed(ProtocolStack.this, bytes);
                            }
                        }
                    });

        }
//        if (Looper.myLooper() == Looper.getMainLooper()) {
//            Iterator<WriteSucceedListener> itor = ((ArrayList) writeSucceedListeners.clone()).iterator();
//            byte[] cmdExecuted = getCmdExecuted();
//            while (itor.hasNext()) {
//                WriteSucceedListener listener = itor.next();
//                listener.writeSucceed(this, cmdExecuted);
//            }
//        } else {
//            // Run this function on the main thread.
//            byte[] copy = Arrays.copyOf(cmdExcuting, cmdExcuting.length);
//            cmdListExcuted.add(copy);
//            Handler mainHandler = new Handler(AppContext.getInstance().getMainLooper());
//            mainHandler.post(new Runnable() {
//                @Override
//                public void run() {
//                    notifyWriteSucceedListeners();
//                }
//            });
//        }
    }

    public void registerSuccessRateListener(SuccessRateListener srl) {
        if (srl != null && !successRateListeners.contains(srl))
            successRateListeners.add(srl);
    }

    public void unregisterSuccessRateListener(SuccessRateListener srl) {
        if (srl != null)
            successRateListeners.remove(srl);
    }

    public void notifySuccessRateListeners() {
        if (cmdExcuting != null) {
            Observable.just(1).observeOn(AndroidSchedulers.mainThread())
                    .subscribe(new Consumer<Integer>() {
                        @Override
                        public void accept(Integer integer) throws Throwable {
                            Iterator<SuccessRateListener> itor = ((ArrayList) successRateListeners.clone()).iterator();
                            while (itor.hasNext()) {
                                SuccessRateListener listener = itor.next();
                                listener.successRate(ProtocolStack.this);
                            }
                        }
                    });

        }
//        if (Looper.myLooper() == Looper.getMainLooper()) {
//            Iterator<SuccessRateListener> itor = ((ArrayList) successRateListeners.clone()).iterator();
//            while (itor.hasNext()) {
//                SuccessRateListener listener = itor.next();
//                listener.successRate(this);
//            }
//        } else {
//            // Run this function on the main thread.
//            Handler mainHandler = new Handler(AppContext.getInstance().getMainLooper());
//            mainHandler.post(new Runnable() {
//                @Override
//                public void run() {
//                    notifySuccessRateListeners();
//                }
//            });
//        }
    }

    public boolean isComStateOK() {
        return isComStateOK;
    }

    public void setComState(boolean comState) {
        this.isComStateOK = comState;
    }

    public void setState(ProtocolStackState state) {
        this.state = state;
    }

    public void send() {
        state.handleSend(this);
    }

    public void recv(byte[] data) {
        state.handleRecv(this, data);
    }

    public void process(byte[] data) {
        Message recvMsg = Message.obtain();
        recvMsg.what = InnerHandler.RECV;
        Bundle bd = new Bundle();
        bd.putByteArray("data", data);
        recvMsg.setData(bd);
        innerHandler.sendMessage(recvMsg);
    }

    public Message createMsg(byte[] data) {
        Message msg = Message.obtain();
        Bundle bd = new Bundle();
        bd.putByteArray("data", data);
        return msg;
    }

    /**
     * Put priority command.
     */
    public void putPriorityCmd(final byte[] cmd) {
        if (cmd == null)
            return;

        Runnable run = new Runnable() {
            @Override
            public void run() {
                if (cmdListPriority.size() < 2000) {
                    cmdListPriority.add(cmd);
                    // Log.d("test---", "putPriorityCmd checkStackIdle()=" +
                    // checkStackIdle());
                    if (checkStackIdle()) {
                        setIdle(false);
                        innerHandler.sendEmptyMessage(InnerHandler.SEND);
                        // Log.d("test---", "putPriorityCmd14 + setIdle(false)" );
                        // setIdle(false);
                    }
                } else {
                    Log.e(TAG, "Priority command list is full!");
                }
            }
        };
        innerHandler.post(run);
    }

    public void putPriorityCmd(final List<byte[]> cmdList) {
        if (cmdList == null)
            return;
        Runnable run = new Runnable() {
            @Override
            public void run() {
                if (cmdListPriority.size() < 2000) {
                    cmdListPriority.addAll(cmdList);
                    // Log.d("test---", "putPriorityCmd13 + addAll" );
                    if (checkStackIdle()) {
                        // Log.d("test---", "putPriorityCmd13 + checkStackIdle()" );
                        setIdle(false);
                        innerHandler.sendEmptyMessage(InnerHandler.SEND);
                        // Log.d("test---", "putPriorityCmd13 + setIdle(false)" );
                    }
                } else {
                    Log.e(TAG, "Priority command list is full!");
                }
            }
        };
        innerHandler.post(run);
    }

    /**
     * Put Normal command. when there are priority commands, normal command will
     * not be execute.
     */
    public void putNormalCmd(final byte[] cmd) {
        if (cmd == null)
            return;

        Runnable run = new Runnable() {
            @Override
            public void run() {
                if (cmdListNormal.size() < 2000) {
                    cmdListNormal.add(cmd);
                    if (checkStackIdle()) {
                        setIdle(false);
                        innerHandler.sendEmptyMessage(InnerHandler.SEND);
                        // Log.d("test---", "putNormalCmd11 + setIdle(false)" );
                    }
                } else {
                    Log.e(TAG, "Normal command list is full!");
                }
            }
        };
        innerHandler.post(run);

    }

    public void putNormalCmd(final List<byte[]> cmdList) {
        if (cmdList == null)
            return;

        Runnable run = new Runnable() {
            @Override
            public void run() {
                if (cmdListNormal.size() < 2000) {
                    cmdListNormal.addAll(cmdList);
                    if (checkStackIdle()) {
                        setIdle(false);
                        innerHandler.sendEmptyMessage(InnerHandler.SEND);
                        // Log.d("test---", "putNormalCmd12 + setIdle(false)" );
                    }
                } else {
                    Log.e(TAG, "Normal command list is full!");
                }
            }
        };
        innerHandler.post(run);

    }

    public boolean checkStackIdle() {
        // Log.d("test----", "checkStackIdle:isIdle=" + isIdle);
        return isIdle;
    }

    public synchronized void setIdle(boolean idle) {
        isIdle = idle;
        // Log.d("test----", "setIdle:isIdle=" + isIdle);
    }

    public byte[] takeCmd() {
        if (totalExectuteNum > 100) {
            if (getSuccessRate() < 90.0f) {
                notifySuccessRateListeners();
            }
        }
        if (isComStateOK()) {
            if (cmdListPriority.size() != 0) {
                totalExectuteNum++;
                cmdExcuting = cmdListPriority.remove(0);
                return cmdExcuting;
            } else if (cmdListNormal.size() != 0) {
                totalExectuteNum++;
                cmdExcuting = cmdListNormal.remove(0);
                return cmdExcuting;
            } else {
                cmdExcuting = null;
                setIdle(true);
                return null;
            }
        } else {
            totalExectuteNum++;
            return cmdExcuting;
        }
    }

    public byte[] getCmdExecuting() {
        return cmdExcuting;
    }

    public byte[] getCmdExecuted() {
        if (cmdListExcuted.size() > 0) {
            return cmdListExcuted.remove(0);
        } else {
            return null;
        }
    }

    public int succeedCount() {
        return totalSucceedNum;
    }

    public int failedCount() {
        return totalExectuteNum - totalSucceedNum;
    }

    public int totalCount() {
        return totalExectuteNum;
    }

    public float getSuccessRate() {
        return succeedCount() * 100.0f / totalCount();
    }

    public Handler getStackHandler() {
        return innerHandler;
    }

    public void setComparator(Comparator<byte[]> comparator) {
        this.comparator = comparator;
    }

    public void clearCmdbyComparator(byte[] cmd) {
        ArrayList<byte[]> deleteList = new ArrayList<byte[]>();

        Iterator<byte[]> it = cmdListNormal.iterator();
        while (it.hasNext()) {
            byte[] cmdInQueue = it.next();
            if (comparator.compare(cmd, cmdInQueue) == 0) {
                deleteList.add(cmdInQueue);
                spindleNum.add((int) cmdInQueue[1]);
            } else {
                spindleNum.remove((int) cmdInQueue[1]);
            }

        }
        cmdListNormal.removeAll(deleteList);
        deleteList.clear();
        it = cmdListPriority.iterator();
        while (it.hasNext()) {
            byte[] cmdInQueue = it.next();
            if (comparator.compare(cmd, cmdInQueue) == 0) {
                deleteList.add(cmdInQueue);
                spindleNum.add((int) cmdInQueue[1]);
            } else {
                spindleNum.remove((int) cmdInQueue[1]);
            }
        }
        cmdListPriority.removeAll(deleteList);
    }

    public void clearNormalCmdList() {
        cmdListNormal.clear();
    }

    public void clearPriorityCmdList() {
        cmdListPriority.clear();
    }

    public void clearAllCmdList() {
        clearNormalCmdList();
        clearPriorityCmdList();
        setIdle(true);
    }

    class InnerHandler extends Handler {
        public static final int SEND = 0;
        public static final int RECV = 1;

        public InnerHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case SEND:
                    send();
                    break;
                case RECV:
                    recv(msg.getData().getByteArray("data"));
                    break;
                default:
                    break;
            }
        }
    }

}
